﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Routing;
using Basmus.Resources;
using Basmus.Web;
using Basmus.Web.Topology;

namespace Basmus.Values.Web
{
    public class WebLinkService
    {
        private readonly object _lock = new object();

        private readonly IValuesService<string> _stringValuesService;
        private readonly IRouteInfoService _routeInfoService;
        private readonly IContextService _contextService;
        private readonly IVirtualPathService _virtualPathService;

        private readonly bool _toLowerDisable;


        private readonly IDictionary<Type, TargetTypeInfo> _typeInfos = new Dictionary<Type, TargetTypeInfo>();

        private readonly IDictionary<IApplication, RouteCollectionWrapper> _appRouteCollections =
            new Dictionary<IApplication, RouteCollectionWrapper>();


        public WebLinkService(IVirtualPathService virtualPathService, IValuesService<string> stringValuesService, IRouteInfoService routeInfoService, IContextService contextService, bool toLowerDisable = false)
        {
            _stringValuesService = stringValuesService;
            _routeInfoService = routeInfoService;
            _contextService = contextService;
            _virtualPathService = virtualPathService;
            _toLowerDisable = toLowerDisable;
        }

        public IDomain CurrentDomain { get; set; }

        public void LoadTopology(ITopology topology)
        {
            lock (_lock)
            {
                var domainDeclarations = topology
                    .DomainDeclarations()
                    .ToArray();
                var appDeclarations = domainDeclarations
                    .SelectMany(x => x.AppDeclarations());
                foreach (var appDeclaration in appDeclarations)
                {
                    var appRouteCollection = new RouteCollectionWrapper(appDeclaration.AppVirtualPath,
                                                                    appDeclaration.RouteExistingFiles);
                    if (_appRouteCollections.ContainsKey(appDeclaration))
                    {
                        _appRouteCollections[appDeclaration] = appRouteCollection;
                    }
                    else
                    {
                        _appRouteCollections.Add(appDeclaration,
                                                 appRouteCollection);
                    }
                    foreach (var targetType in appDeclaration.TargetTypes())
                    {
                        var virtualPath = _virtualPathService.Resolve(targetType);
                        var info = new TargetTypeInfo(targetType, appDeclaration, virtualPath);
                        if (_typeInfos.ContainsKey(targetType))
                        {
                            _typeInfos[targetType] = info;
                        }
                        else
                        {
                            _typeInfos.Add(targetType, info);
                        }
                        if (virtualPath != null)
                        {
                            foreach (var modelContext in _contextService.GetModelContexts(targetType))
                            {
                                var routeInfo = _routeInfoService.GetRouteInfo(modelContext);
                                if (routeInfo != null)
                                {
                                    var pageRouteHandler = new PageRouteHandler(virtualPath,
                                                                                routeInfo.CheckPhysicalUrlAccess);
                                    var routeName = RoutingHelper.GetRouteName(targetType, modelContext.SchemeKey);
                                    if (appRouteCollection.RouteCollection[routeName] == null)
                                    {
                                        var routeValueDictionary = new RouteValueDictionary
                                                                       {
                                                                           {
                                                                               RoutingHelper.SchemeKeyDataTokenName,
                                                                               modelContext.SchemeKey
                                                                               }
                                                                       };
                                        var url = routeInfo.Url.TrimStart('/');
                                        var item = new Route(url, null, null, routeValueDictionary, pageRouteHandler);
                                        appRouteCollection.RouteCollection.Add(routeName, item);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        public string GetUrl(Type modelType, object model)
        {
            return GetUrl(modelType, SchemeKey.Default, model);
        }

        public string GetUrl(Type modelType, SchemeKey schemeKey, object model)
        {
            if (_typeInfos.ContainsKey(modelType))
            {
                var info = _typeInfos[modelType];
                var appRouteCollection = _appRouteCollections[info.Application];
                var routeName = RoutingHelper.GetRouteName(modelType, schemeKey);
                var valueProvider = _stringValuesService.GetValues(new ModelContext(modelType, schemeKey), model);

                //try build url using routing
                var routeValueDictionary = valueProvider.ToRouteValueDictionary();
                var virtualPathData = appRouteCollection.GetVirtualPath(routeName, routeValueDictionary);
                
                if (virtualPathData != null && virtualPathData.VirtualPath != null)
                {
                    var virtualPath = virtualPathData.VirtualPath;
                   
                    //ends with slash check
                    var routeInfo = _routeInfoService.GetRouteInfo(new ModelContext(modelType, schemeKey));
                    if (routeInfo.Url.EndsWith("/"))
                    {
                        var splitPath = virtualPath.Split('?');
                        splitPath[0] = splitPath[0] + (splitPath[0].EndsWith("/") ? "" : "/");
                        virtualPathData.VirtualPath = splitPath[0] + (splitPath.Length > 1 ? "?" + splitPath[1] : "");
                    }

                    //toLowerDisable check
                    if (_toLowerDisable)
                    {
                        var splitPath = virtualPath.Split('?');
                        splitPath[0] = splitPath[0].ToLower();
                        virtualPathData.VirtualPath = splitPath[0] + (splitPath.Length > 1 ? "?" + splitPath[1] : "");
                    }

                    return virtualPath;
                }

                //try to build url using PhysicalUrl
                if (info.HasPhysicalUrl)
                {
                    var url = info.VirtualPath;
                    url = url.Replace("~/", info.Application.AppVirtualPath);
                    var queryString = valueProvider.ToQueryStringWithMultiValues(",");
                    if (!string.IsNullOrWhiteSpace(queryString))
                    {
                        url = url + "?" + queryString;
                    }
                    return url;
                }
            }
            return null;
        }

        public void SetModel(Type modelType, object model, IValuesProvider<string> stringValuesProvider)
        {
            SetModel(modelType, SchemeKey.Default, model, stringValuesProvider);
        }

        public void SetModel(Type modelType, SchemeKey schemeKey, object model, IValuesProvider<string> stringValuesProvider)
        {
            _stringValuesService.SetValues(new ModelContext(modelType, schemeKey), model, stringValuesProvider);
        }
    }
}